package com.hbfec.fileserver.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 缓存Service
 */
@Service
public class CacheService {
    @Autowired
    private SimpleToolsService tools;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * redis get
     *
     * @param key 键
     * @return 值
     */
    public String get(String key) {
        return stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                return stringRedisConn.get(key);
            }
        });
    }

    /**
     * redis lpush
     *
     * @param statusKey   状态键
     * @param statusValue 状态值
     * @param queueKey    列表键
     * @param queueValue  列表项
     * @return 结果
     */
    public Boolean lpush(String statusKey, String statusValue, String queueKey, String... queueValue) {
        List<Object> result = stringRedisTemplate.execute(new RedisCallback<List<Object>>() {
            @Override
            public List<Object> doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                stringRedisConn.openPipeline();
                stringRedisConn.multi();
                stringRedisConn.lPush(queueKey, queueValue);
                stringRedisConn.set(statusKey, statusValue);
                stringRedisConn.exec();
                return stringRedisConn.closePipeline();
            }
        });
        if (result != null && result.size() == 1 && (result.get(0) instanceof List)) {
            return true;
        }
        return false;
    }

    /**
     * 验证key在缓存中是否存在，如果存在，则继续等待，直到不存在为止
     *
     * @param key        键
     * @param countLimit 验证次数上限
     * @param freq       验证频率，单位是毫秒
     * @return 结果
     */
    public Boolean exists(String key, int countLimit, long freq) {
        Boolean result = stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                return stringRedisConn.exists(key);
            }
        });
        int count = 0;
        while (count++ < countLimit && result) {
            try {
                Thread.sleep(freq);
            } catch (InterruptedException ex) { // 中断异常直接吞掉
            }
            result = stringRedisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                    return stringRedisConn.exists(key);
                }
            });
        }
        return result;
    }

    /**
     * redis lrange
     *
     * @param key 列表键
     * @return 列表全部数据
     */
    public List lrangeAll(String key) {
        return stringRedisTemplate.execute(
                new RedisCallback<List>() {
                    @Override
                    public List doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                        return stringRedisConn.lRange(key, 0, -1);
                    }
                });
    }

    /**
     * redis rpoplpush
     *
     * @param key1   pop列表键
     * @param key2   push列表键
     * @param number 数据量
     * @return pop的全部数据
     */
    public List rpoplpush(String key1, String key2, int number) {
        return stringRedisTemplate.executePipelined(
                new RedisCallback<Object>() {
                    @Override
                    public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                        for (int i = 0; i < number; ++i) {
                            stringRedisConn.rPopLPush(key1, key2);
                        }
                        return null;
                    }
                });
    }

    /**
     * redis del
     *
     * @param key 删除的键
     */
    public void del(String... key) {
        stringRedisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                return stringRedisConn.del(key);
            }
        });
    }

    /**
     * 获取锁
     *
     * @param key    键
     * @param expire 锁的过期时间
     * @return 获取锁成功，返回一个随机数，解锁时需要传入该随机数；获取锁失败，返回null
     */
    public String getLock(String key, int expire) {
        final String value = tools.generateUUID();
        final String script = "return redis.call('set',KEYS[1],ARGV[1],ARGV[2],ARGV[3],ARGV[4])";
        String status = stringRedisTemplate.execute(
                new RedisCallback<String>() {
                    @Override
                    public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                        return (String) stringRedisConn.eval(script, ReturnType.STATUS, 1,
                                key, value, "NX", "EX", String.valueOf(expire / 1000));
                    }
                });
        if ("OK".equals(status)) {
            return value;
        }
        return null;
    }

    /**
     * 释放锁
     *
     * @param key   键
     * @param value 值
     * @return 释放成功返回true
     */
    public Boolean releaseLock(String key, String value) {
        final String script =
                "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long ret = stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        StringRedisConnection stringRedisConn = (StringRedisConnection) redisConnection;
                        return (Long) stringRedisConn.eval(script, ReturnType.INTEGER, 1, key, value);
                    }
                });
        return ret == 1L;
    }
}
